﻿using Org.BouncyCastle.Crypto;

namespace PmSoft.Core.Cryptos;

public abstract class GeneralDigest : IDigest
{
    private const int BYTE_LENGTH = 64;

    private readonly byte[] _xBuf;
    private int xBufOff;

    private long byteCount;

    internal GeneralDigest()
    {
        _xBuf = new byte[4];
    }

    internal GeneralDigest(GeneralDigest t)
    {
        _xBuf = new byte[t._xBuf.Length];
        Array.Copy(t._xBuf, 0, _xBuf, 0, t._xBuf.Length);

        xBufOff = t.xBufOff;
        byteCount = t.byteCount;
    }

    public void Update(byte input)
    {
        _xBuf[xBufOff++] = input;

        if (xBufOff == _xBuf.Length)
        {
            ProcessWord(_xBuf, 0);
            xBufOff = 0;
        }

        byteCount++;
    }

    public void BlockUpdate(byte[] input, int inOff, int length)
    {
        // fill the current word
        while ((xBufOff != 0) && (length > 0))
        {
            Update(input[inOff]);
            inOff++;
            length--;
        }

        // process whole words.
        while (length > _xBuf.Length)
        {
            ProcessWord(input, inOff);

            inOff += _xBuf.Length;
            length -= _xBuf.Length;
            byteCount += _xBuf.Length;
        }

        // load in the remainder.
        while (length > 0)
        {
            Update(input[inOff]);

            inOff++;
            length--;
        }
    }

    public void Finish()
    {
        long bitLength = (byteCount << 3);

        // add the pad bytes.
        Update(unchecked((byte)128));

        while (xBufOff != 0) Update(unchecked((byte)0));
        ProcessLength(bitLength);
        ProcessBlock();
    }

    public virtual void Reset()
    {
        byteCount = 0;
        xBufOff = 0;
        Array.Clear(_xBuf, 0, _xBuf.Length);
    }

    public int GetByteLength()
    {
        return BYTE_LENGTH;
    }

    internal abstract void ProcessWord(byte[] input, int inOff);

    internal abstract void ProcessLength(long bitLength);

    internal abstract void ProcessBlock();

    public abstract string AlgorithmName { get; }

    public abstract int GetDigestSize();

    public abstract void BlockUpdate(ReadOnlySpan<byte> input);

    public abstract int DoFinal(byte[] output, int outOff);

    public abstract int DoFinal(Span<byte> output);
}